iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 5
1

Room

有些資料不需連網,只需存在手機的話
可在手機上建立資料庫

主要包含的有三部分

  • Entity
  • DAO
  • Database

Entity

定義表的結構,建立一個data class
每個Entity類似試算表中的每個row(horizontal)
其中包含各種屬性column(vertical)表格欄位
同個class全部的Entity組成就像是一份資料表

例如有一份睡眠統計資料,首先建立一個SleepNight.kt

data class SleepNight(
        var nightId : Long = 0L,
        val startTimeMilli : Long = System.currentTimeMillis(),
        var endTimeMilli : Long = startTimeMilli,
        var sleepQuality : Int = -1
)

加入相關的註解,做成Entity如下

//註解表示這是一份@Entity,並且使用參數tableName命名爲"daily_sleep_quality_table"
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
        //每個資料都必須有個唯一值用於識別
        //參數autoGenerate使Room自動爲每個Entity產生
        @PrimaryKey(autoGenerate = true)
        var nightId: Long = 0L,
        
        //以下定義Entity的各屬性
        @ColumnInfo(name = "start_time_milli")
        val startTimeMilli: Long = System.currentTimeMillis(),

        @ColumnInfo(name = "end_time_milli")
        var endTimeMilli: Long = startTimeMilli,

        @ColumnInfo(name = "quality_rating")
        var sleepQuality: Int = -1
)

DAO

data access object
建立用來操作資料庫的interface
提供例如@Query,@Insert,@Delete,@Update等annotation定義操作資料庫
如使用@Insert,@Update時Room會產生所需的對應方法
使用其它annotation時,編譯器也會協助檢查SQL語法

新增一個SleepDatabaseDao.kt,在宣告interface前加入@Dao

@Dao
interface SleepDataDao{}

然後在此interface中加入相對功能的annotation

@Dao
interface SleepDatabaseDao {
    @Insert
    fun insert(night: SleepNight)

    @Update
    fun update(night: SleepNight)

    //從daily_sleep_quality_table查詢,nightId符合get()中的key
    @Query("SELECT * FROM daily_sleep_quality_table WHERE nightId = :key")
    fun get(key: Long): SleepNight?

    //清除全部資料表
    @Query("DELETE FROM daily_sleep_quality_table")
    fun clear()

    //依nightId將資料表做降序(descending大到小),LIMIT 1表示只回傳一個項目
    //getTonight()回傳可空的SleepNight,因爲資料表在一開始與全部清除時,都會是空的
    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
    fun getTonight(): SleepNight?

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
    fun getAllNights(): LiveData<List<SleepNight>>
}

清除還可使用@Delete,但主要用於知道要清除資料表中的某個內容
如果要清除整個資料表的內容,還是使用@Query比較有效率

Database

建立爲abstract class的資料庫以存放data class與DAO
一般程序爲:

  • Create a public abstract class that extends RoomDatabase. This class is to act as a database holder. The class is abstract, because Room creates the implementation for you.
  • Annotate the class with @Database. In the arguments, declare the entities for the database and set the version number.
  • Inside a companion object, define an abstract method or property that returns a SleepDatabaseDao. Room will generate the body for you.
  • You only need one instance of the Room database for the whole app, so make the RoomDatabase a singleton.
  • Use Room's database builder to create the database only if the database doesn't exist. Otherwise, return the existing database.
    步驟如下:
    新增一個SleepDatabase.kt檔
    建立一個繼承自RoomDatabase的abstract class
@Database()
abstract class SleepDatabase : RoomDatabase() {}

@Database須要相關的參數於()中

  • entities 使用先前已用Entity註解的data class
  • version 每次更改架構時,必須增加版本號
  • exportSchema 設false爲不保存架構更改記錄
    database須要以DAO存取,所以在database內宣告SleepDatabaseDao的變數
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
    abstract val sleepDatabaseDao: SleepDatabaseDao
}

在database中建立一個companion object,並宣告一個可空變數INSTANCE
此變數會固定保存對資料庫的參考,可避免重複連結資料庫而降低效能
而以Volatile註解表示此INSTANCE變數僅會在主記憶體中讀寫
可避免進入cache被不同執行緒讀寫而造成問題

@Volatile
private var INSTANCE: SleepDatabase? = null

接着再定義一個getInstance(ontext: Context)方法,在此方法中加入synchronized{}區塊,並透過this參數以存取這個context

利用synchronized{}區塊,表示一次僅可一個執行緒進入此區塊初始化資料庫
可避免若有多個執行緒同時要建立資料庫的情況

在synchronized{}內宣告一變數var instance = INSTANCE
指向INSTANCE爲僅在此使用的方式
接着判斷instance是否null,若爲null則使用Room.databaseBuilder建立資料庫
(範例呼叫的fallbackToDestructiveMigration()表示破壞與重建資料庫)

abstract val sleepDatabaseDao: SleepDatabaseDao

    companion object {

        @Volatile
        private var INSTANCE: SleepDatabase? = null

        fun getInstance(context: Context): SleepDatabase {
            synchronized(this) {
                var instance = INSTANCE

                if (instance == null) {
                    instance = Room.databaseBuilder(
                            context.applicationContext,
                            SleepDatabase::class.java,
                            "sleep_history_database"
                    )
                            .fallbackToDestructiveMigration()
                            .build()
//                    如果INSTANCE是空,透過instance建立之後,再丟給INSTANCE
                    INSTANCE = instance
                }
                return instance
            }
        }
    }

最後return intstance

  • 以上的code可做於建立其它Room database時的範本復用

TEST

前面已經完成資料庫的建置步驟

現在使用測試檢視資料庫以確保有置成功

  • 首先在androidTest的package新增一個test class
    在此class name之前,以@RunWith(AndroidJUnit4::class)註解表示以下開始測試程序
  • 再來以@Before註解的createDb()函數表示先在記憶體中建立資料庫,這只是暫時性的,並未真的建立在裝置的檔案系統中,測試結束後會從記憶體清除
  • @Test註解的函數中,新增,插入SleepNight的entity
  • 測試結束後,以@After註解的函數關閉資料庫
    以下的範例程式碼也是一般測試通用的,可當作範本復用
@RunWith(AndroidJUnit4::class)
class SleepDatabaseTest {

    private lateinit var sleepDao: SleepDatabaseDao
    private lateinit var db: SleepDatabase

    @Before
    fun createDb() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        // Using an in-memory database because the information stored here disappears when the
        // process is killed.
        db = Room.inMemoryDatabaseBuilder(context, SleepDatabase::class.java)
                // Allowing main thread queries, just for testing.
                .allowMainThreadQueries()
                .build()
        sleepDao = db.sleepDatabaseDao
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    @Throws(Exception::class)
    fun insertAndGetNight() {
        val night = SleepNight()
        sleepDao.insert(night)
        val tonight = sleepDao.getTonight()
        assertEquals(tonight?.sleepQuality, -1)
    }
}

最後在此測試的test.kt檔按右鍵run執行,檢視是否通過

https://enginebai.com/2019/04/03/android-database-room/

https://codelabs.developers.google.com/codelabs/kotlin-android-training-room-database/index.html?index=..%2F..android-kotlin-fundamentals#5


上一篇
Day 4--AlertDialog(二)
下一篇
Day 6--Room(二) 實作,爲app建立一個資料庫,新增資料存入
系列文
程式初學:Android與Kotlin30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言